""" DIRECT Nine DoF Manipulation Panel """

__all__ = ['Placer', 'place']

# Import Tkinter, Pmw, and the dial code from this directory tree.
from panda3d.core import NodePath, Vec3
from direct.tkwidgets.AppShell import AppShell
from direct.tkwidgets import Dial
from direct.tkwidgets import Floater
from direct.directtools.DirectGlobals import ZERO_VEC, UNIT_VEC
from direct.showbase.MessengerGlobal import messenger
from direct.showbase import ShowBaseGlobal
from direct.task.TaskManagerGlobal import taskMgr
import Pmw
import tkinter as tk

#TODO: Task to monitor pose


class Placer(AppShell):
    # Override class variables here
    appname = 'Placer Panel'
    frameWidth      = 625
    frameHeight     = 215
    usecommandarea = 0
    usestatusarea  = 0

    def __init__(self, parent = None, **kw):
        INITOPT = Pmw.INITOPT
        optiondefs = (
            ('title',       self.appname,       None),
            ('nodePath',    ShowBaseGlobal.direct.camera,      None),
        )
        self.defineoptions(kw, optiondefs)

        # Call superclass initialization function
        AppShell.__init__(self)

        self.initialiseoptions(Placer)

    def appInit(self):
        # Initialize state
        self.tempCS = ShowBaseGlobal.direct.group.attachNewNode('placerTempCS')
        self.orbitFromCS = ShowBaseGlobal.direct.group.attachNewNode(
            'placerOrbitFromCS')
        self.orbitToCS = ShowBaseGlobal.direct.group.attachNewNode('placerOrbitToCS')
        self.refCS = self.tempCS

        # Dictionary keeping track of all node paths manipulated so far
        self.nodePathDict = {}
        self.nodePathDict['camera'] = ShowBaseGlobal.direct.camera
        self.nodePathDict['widget'] = ShowBaseGlobal.direct.widget
        self.nodePathNames = ['camera', 'widget', 'selected']

        self.refNodePathDict = {}
        self.refNodePathDict['parent'] = self['nodePath'].getParent()
        self.refNodePathDict['render'] = render
        self.refNodePathDict['camera'] = ShowBaseGlobal.direct.camera
        self.refNodePathDict['widget'] = ShowBaseGlobal.direct.widget
        self.refNodePathNames = ['parent', 'self', 'render',
                                 'camera', 'widget', 'selected']

        # Initial state
        self.initPos = Vec3(0)
        self.initHpr = Vec3(0)
        self.initScale = Vec3(1)
        self.deltaHpr = Vec3(0)

        # Offset for orbital mode
        self.posOffset = Vec3(0)

        # Set up event hooks
        self.undoEvents = [('DIRECT_undo', self.undoHook),
                           ('DIRECT_pushUndo', self.pushUndoHook),
                           ('DIRECT_undoListEmpty', self.undoListEmptyHook),
                           ('DIRECT_redo', self.redoHook),
                           ('DIRECT_pushRedo', self.pushRedoHook),
                           ('DIRECT_redoListEmpty', self.redoListEmptyHook)]
        for event, method in self.undoEvents:
            self.accept(event, method)

        # Init movement mode
        self.movementMode = 'Relative To:'

    def createInterface(self):
        # The interior of the toplevel panel
        interior = self.interior()
        interior['relief'] = tk.FLAT
        # Add placer commands to menubar
        self.menuBar.addmenu('Placer', 'Placer Panel Operations')
        self.menuBar.addmenuitem('Placer', 'command',
                            'Zero Node Path',
                            label = 'Zero All',
                            command = self.zeroAll)
        self.menuBar.addmenuitem('Placer', 'command',
                            'Reset Node Path',
                            label = 'Reset All',
                            command = self.resetAll)
        self.menuBar.addmenuitem('Placer', 'command',
                            'Print Node Path Info',
                            label = 'Print Info',
                            command = self.printNodePathInfo)
        self.menuBar.addmenuitem(
            'Placer', 'command',
            'Toggle widget visability',
            label = 'Toggle Widget Vis',
            command = ShowBaseGlobal.direct.toggleWidgetVis)
        self.menuBar.addmenuitem(
            'Placer', 'command',
            'Toggle widget manipulation mode',
            label = 'Toggle Widget Mode',
            command = ShowBaseGlobal.direct.manipulationControl.toggleObjectHandlesMode)

        # Get a handle to the menu frame
        menuFrame = self.menuFrame
        self.nodePathMenu = Pmw.ComboBox(
            menuFrame, labelpos = tk.W, label_text = 'Node Path:',
            entry_width = 20,
            selectioncommand = self.selectNodePathNamed,
            scrolledlist_items = self.nodePathNames)
        self.nodePathMenu.selectitem('selected')
        self.nodePathMenuEntry = (
            self.nodePathMenu.component('entryfield_entry'))
        self.nodePathMenuBG = (
            self.nodePathMenuEntry.configure('background')[3])
        self.nodePathMenu.pack(side = 'left', fill = 'x', expand = 1)
        self.bind(self.nodePathMenu, 'Select node path to manipulate')

        modeMenu = Pmw.OptionMenu(menuFrame,
                                  items = ('Relative To:',
                                           'Orbit:'),
                                  initialitem = 'Relative To:',
                                  command = self.setMovementMode,
                                  menubutton_width = 8)
        modeMenu.pack(side = 'left', expand = 0)
        self.bind(modeMenu, 'Select manipulation mode')

        self.refNodePathMenu = Pmw.ComboBox(
            menuFrame, entry_width = 16,
            selectioncommand = self.selectRefNodePathNamed,
            scrolledlist_items = self.refNodePathNames)
        self.refNodePathMenu.selectitem('parent')
        self.refNodePathMenuEntry = (
            self.refNodePathMenu.component('entryfield_entry'))
        self.refNodePathMenu.pack(side = 'left', fill = 'x', expand = 1)
        self.bind(self.refNodePathMenu, 'Select relative node path')

        self.undoButton = tk.Button(menuFrame, text = 'Undo',
                                    command = ShowBaseGlobal.direct.undo)
        if ShowBaseGlobal.direct.undoList:
            self.undoButton['state'] = 'normal'
        else:
            self.undoButton['state'] = 'disabled'
        self.undoButton.pack(side = 'left', expand = 0)
        self.bind(self.undoButton, 'Undo last operation')

        self.redoButton = tk.Button(menuFrame, text = 'Redo',
                                    command = ShowBaseGlobal.direct.redo)
        if ShowBaseGlobal.direct.redoList:
            self.redoButton['state'] = 'normal'
        else:
            self.redoButton['state'] = 'disabled'
        self.redoButton.pack(side = 'left', expand = 0)
        self.bind(self.redoButton, 'Redo last operation')

        # Create and pack the Pos Controls
        posGroup = Pmw.Group(interior,
                             tag_pyclass = tk.Menubutton,
                             tag_text = 'Position',
                             tag_font=('MSSansSerif', 14),
                             tag_activebackground = '#909090',
                             ring_relief = tk.RIDGE)
        posMenubutton = posGroup.component('tag')
        self.bind(posMenubutton, 'Position menu operations')
        posMenu = tk.Menu(posMenubutton, tearoff = 0)
        posMenu.add_command(label = 'Set to zero', command = self.zeroPos)
        posMenu.add_command(label = 'Reset initial',
                            command = self.resetPos)
        posMenubutton['menu'] = posMenu
        posGroup.pack(side='left', fill = 'both', expand = 1)
        posInterior = posGroup.interior()

        # Create the dials
        self.posX = self.createcomponent('posX', (), None,
                                         Floater.Floater, (posInterior,),
                                         text = 'X', relief = tk.FLAT,
                                         value = 0.0,
                                         label_foreground = 'Red')
        self.posX['commandData'] = ['x']
        self.posX['preCallback'] = self.xformStart
        self.posX['postCallback'] = self.xformStop
        self.posX['callbackData'] = ['x']
        self.posX.pack(expand=1, fill='both')

        self.posY = self.createcomponent('posY', (), None,
                                         Floater.Floater, (posInterior,),
                                         text = 'Y', relief = tk.FLAT,
                                         value = 0.0,
                                         label_foreground = '#00A000')
        self.posY['commandData'] = ['y']
        self.posY['preCallback'] = self.xformStart
        self.posY['postCallback'] = self.xformStop
        self.posY['callbackData'] = ['y']
        self.posY.pack(expand=1, fill='both')

        self.posZ = self.createcomponent('posZ', (), None,
                                         Floater.Floater, (posInterior,),
                                         text = 'Z', relief = tk.FLAT,
                                         value = 0.0,
                                         label_foreground = 'Blue')
        self.posZ['commandData'] = ['z']
        self.posZ['preCallback'] = self.xformStart
        self.posZ['postCallback'] = self.xformStop
        self.posZ['callbackData'] = ['z']
        self.posZ.pack(expand=1, fill='both')

        # Create and pack the Hpr Controls
        hprGroup = Pmw.Group(interior,
                             tag_pyclass = tk.Menubutton,
                             tag_text = 'Orientation',
                             tag_font=('MSSansSerif', 14),
                             tag_activebackground = '#909090',
                             ring_relief = tk.RIDGE)
        hprMenubutton = hprGroup.component('tag')
        self.bind(hprMenubutton, 'Orientation menu operations')
        hprMenu = tk.Menu(hprMenubutton, tearoff = 0)
        hprMenu.add_command(label = 'Set to zero', command = self.zeroHpr)
        hprMenu.add_command(label = 'Reset initial', command = self.resetHpr)
        hprMenubutton['menu'] = hprMenu
        hprGroup.pack(side='left', fill = 'both', expand = 1)
        hprInterior = hprGroup.interior()

        # Create the dials
        self.hprH = self.createcomponent('hprH', (), None,
                                         Dial.AngleDial, (hprInterior,),
                                         style = 'mini',
                                         text = 'H', value = 0.0,
                                         relief = tk.FLAT,
                                         label_foreground = 'blue')
        self.hprH['commandData'] = ['h']
        self.hprH['preCallback'] = self.xformStart
        self.hprH['postCallback'] = self.xformStop
        self.hprH['callbackData'] = ['h']
        self.hprH.pack(expand=1, fill='both')

        self.hprP = self.createcomponent('hprP', (), None,
                                         Dial.AngleDial, (hprInterior,),
                                         style = 'mini',
                                         text = 'P', value = 0.0,
                                         relief = tk.FLAT,
                                         label_foreground = 'red')
        self.hprP['commandData'] = ['p']
        self.hprP['preCallback'] = self.xformStart
        self.hprP['postCallback'] = self.xformStop
        self.hprP['callbackData'] = ['p']
        self.hprP.pack(expand=1, fill='both')

        self.hprR = self.createcomponent('hprR', (), None,
                                         Dial.AngleDial, (hprInterior,),
                                         style = 'mini',
                                         text = 'R', value = 0.0,
                                         relief = tk.FLAT,
                                         label_foreground = '#00A000')
        self.hprR['commandData'] = ['r']
        self.hprR['preCallback'] = self.xformStart
        self.hprR['postCallback'] = self.xformStop
        self.hprR['callbackData'] = ['r']
        self.hprR.pack(expand=1, fill='both')

        # Create and pack the Scale Controls
        # The available scaling modes
        self.scalingMode = tk.StringVar()
        self.scalingMode.set('Scale Uniform')
        # The scaling widgets
        scaleGroup = Pmw.Group(interior,
                               tag_text = 'Scale Uniform',
                               tag_pyclass = tk.Menubutton,
                               tag_font=('MSSansSerif', 14),
                               tag_activebackground = '#909090',
                               ring_relief = tk.RIDGE)
        self.scaleMenubutton = scaleGroup.component('tag')
        self.bind(self.scaleMenubutton, 'Scale menu operations')
        self.scaleMenubutton['textvariable'] = self.scalingMode

        # Scaling menu
        scaleMenu = tk.Menu(self.scaleMenubutton, tearoff = 0)
        scaleMenu.add_command(label = 'Set to unity',
                              command = self.unitScale)
        scaleMenu.add_command(label = 'Reset initial',
                              command = self.resetScale)
        scaleMenu.add_radiobutton(label = 'Scale Free',
                                      variable = self.scalingMode)
        scaleMenu.add_radiobutton(label = 'Scale Uniform',
                                      variable = self.scalingMode)
        scaleMenu.add_radiobutton(label = 'Scale Proportional',
                                      variable = self.scalingMode)
        self.scaleMenubutton['menu'] = scaleMenu
        # Pack group widgets
        scaleGroup.pack(side='left', fill = 'both', expand = 1)
        scaleInterior = scaleGroup.interior()

        # Create the dials
        self.scaleX = self.createcomponent('scaleX', (), None,
                                           Floater.Floater, (scaleInterior,),
                                           text = 'X Scale',
                                           relief = tk.FLAT,
                                           min = 0.0001, value = 1.0,
                                           resetValue = 1.0,
                                           label_foreground = 'Red')
        self.scaleX['commandData'] = ['sx']
        self.scaleX['callbackData'] = ['sx']
        self.scaleX['preCallback'] = self.xformStart
        self.scaleX['postCallback'] = self.xformStop
        self.scaleX.pack(expand=1, fill='both')

        self.scaleY = self.createcomponent('scaleY', (), None,
                                           Floater.Floater, (scaleInterior,),
                                           text = 'Y Scale',
                                           relief = tk.FLAT,
                                           min = 0.0001, value = 1.0,
                                           resetValue = 1.0,
                                           label_foreground = '#00A000')
        self.scaleY['commandData'] = ['sy']
        self.scaleY['callbackData'] = ['sy']
        self.scaleY['preCallback'] = self.xformStart
        self.scaleY['postCallback'] = self.xformStop
        self.scaleY.pack(expand=1, fill='both')

        self.scaleZ = self.createcomponent('scaleZ', (), None,
                                           Floater.Floater, (scaleInterior,),
                                           text = 'Z Scale',
                                           relief = tk.FLAT,
                                           min = 0.0001, value = 1.0,
                                           resetValue = 1.0,
                                           label_foreground = 'Blue')
        self.scaleZ['commandData'] = ['sz']
        self.scaleZ['callbackData'] = ['sz']
        self.scaleZ['preCallback'] = self.xformStart
        self.scaleZ['postCallback'] = self.xformStop
        self.scaleZ.pack(expand=1, fill='both')

        # Make sure appropriate labels are showing
        self.setMovementMode('Relative To:')
        # Set up placer for inital node path
        self.selectNodePathNamed('init')
        self.selectRefNodePathNamed('parent')
        # Update place to reflect initial state
        self.updatePlacer()
        # Now that you're done setting up, attach commands
        self.posX['command'] = self.xform
        self.posY['command'] = self.xform
        self.posZ['command'] = self.xform
        self.hprH['command'] = self.xform
        self.hprP['command'] = self.xform
        self.hprR['command'] = self.xform
        self.scaleX['command'] = self.xform
        self.scaleY['command'] = self.xform
        self.scaleZ['command'] = self.xform

    ### WIDGET OPERATIONS ###

    def setMovementMode(self, movementMode):
        # Set prefix
        namePrefix = ''
        self.movementMode = movementMode
        if movementMode == 'Relative To:':
            namePrefix = 'Relative '
        elif movementMode == 'Orbit:':
            namePrefix = 'Orbit '
        # Update pos widgets
        self.posX['text'] = namePrefix + 'X'
        self.posY['text'] = namePrefix + 'Y'
        self.posZ['text'] = namePrefix + 'Z'
        # Update hpr widgets
        if movementMode == 'Orbit:':
            namePrefix = 'Orbit delta '
        self.hprH['text'] = namePrefix + 'H'
        self.hprP['text'] = namePrefix + 'P'
        self.hprR['text'] = namePrefix + 'R'
        # Update temp cs and initialize widgets
        self.updatePlacer()

    def setScalingMode(self):
        if self['nodePath']:
            scale = self['nodePath'].getScale()
            if scale[0] != scale[1] or \
               scale[0] != scale[2] or \
               scale[1] != scale[2]:
                self.scalingMode.set('Scale Free')

    def selectNodePathNamed(self, name):
        nodePath = None
        if name == 'init':
            nodePath = self['nodePath']
            # Add Combo box entry for the initial node path
            self.addNodePath(nodePath)
        elif name == 'selected':
            nodePath = ShowBaseGlobal.direct.selected.last
            # Add Combo box entry for this selected object
            self.addNodePath(nodePath)
        else:
            nodePath = self.nodePathDict.get(name, None)
            if nodePath is None:
                # See if this evaluates into a node path
                try:
                    nodePath = eval(name)
                    if isinstance(nodePath, NodePath):
                        self.addNodePath(nodePath)
                    else:
                        # Good eval but not a node path, give up
                        nodePath = None
                except Exception:
                    # Bogus eval
                    nodePath = None
                    # Clear bogus entry from listbox
                    listbox = self.nodePathMenu.component('scrolledlist')
                    listbox.setlist(self.nodePathNames)
            else:
                if name == 'widget':
                    # Record relationship between selected nodes and widget
                    ShowBaseGlobal.direct.selected.getWrtAll()
        # Update active node path
        self.setActiveNodePath(nodePath)

    def setActiveNodePath(self, nodePath):
        self['nodePath'] = nodePath
        if self['nodePath']:
            self.nodePathMenuEntry.configure(
                background = self.nodePathMenuBG)
            # Check to see if node path and ref node path are the same
            if self.refCS is not None and self.refCS == self['nodePath']:
                # Yes they are, use temp CS as ref
                # This calls updatePlacer
                self.setReferenceNodePath(self.tempCS)
                # update listbox accordingly
                self.refNodePathMenu.selectitem('parent')
            else:
                # Record initial value and initialize the widgets
                self.updatePlacer()
            # Record initial position
            self.updateResetValues(self['nodePath'])
            # Set scaling mode based on node path's current scale
            self.setScalingMode()
        else:
            # Flash entry
            self.nodePathMenuEntry.configure(background = 'Pink')

    def selectRefNodePathNamed(self, name):
        nodePath = None
        if name == 'self':
            nodePath = self.tempCS
        elif name == 'selected':
            nodePath = ShowBaseGlobal.direct.selected.last
            # Add Combo box entry for this selected object
            self.addRefNodePath(nodePath)
        elif name == 'parent':
            nodePath = self['nodePath'].getParent()
        else:
            nodePath = self.refNodePathDict.get(name, None)
            if nodePath is None:
                # See if this evaluates into a node path
                try:
                    nodePath = eval(name)
                    if isinstance(nodePath, NodePath):
                        self.addRefNodePath(nodePath)
                    else:
                        # Good eval but not a node path, give up
                        nodePath = None
                except Exception:
                    # Bogus eval
                    nodePath = None
                    # Clear bogus entry from listbox
                    listbox = self.refNodePathMenu.component('scrolledlist')
                    listbox.setlist(self.refNodePathNames)
        # Check to see if node path and ref node path are the same
        if nodePath is not None and nodePath == self['nodePath']:
            # Yes they are, use temp CS and update listbox accordingly
            nodePath = self.tempCS
            self.refNodePathMenu.selectitem('parent')
        # Update ref node path
        self.setReferenceNodePath(nodePath)

    def setReferenceNodePath(self, nodePath):
        self.refCS = nodePath
        if self.refCS:
            self.refNodePathMenuEntry.configure(
                background = self.nodePathMenuBG)
            # Update placer to reflect new state
            self.updatePlacer()
        else:
            # Flash entry
            self.refNodePathMenuEntry.configure(background = 'Pink')

    def addNodePath(self, nodePath):
        self.addNodePathToDict(nodePath, self.nodePathNames,
                               self.nodePathMenu, self.nodePathDict)

    def addRefNodePath(self, nodePath):
        self.addNodePathToDict(nodePath, self.refNodePathNames,
                               self.refNodePathMenu, self.refNodePathDict)

    def addNodePathToDict(self, nodePath, names, menu, dict):
        if not nodePath:
            return
        # Get node path's name
        name = nodePath.getName()
        if name in ['parent', 'render', 'camera']:
            dictName = name
        else:
            # Generate a unique name for the dict
            dictName = name + '-' + repr(hash(nodePath))
        if dictName not in dict:
            # Update combo box to include new item
            names.append(dictName)
            listbox = menu.component('scrolledlist')
            listbox.setlist(names)
            # Add new item to dictionary
            dict[dictName] = nodePath
        menu.selectitem(dictName)

    def updatePlacer(self):
        pos = Vec3(0)
        hpr = Vec3(0)
        scale = Vec3(1)
        np = self['nodePath']
        if np is not None and isinstance(np, NodePath):
            # Update temp CS
            self.updateAuxiliaryCoordinateSystems()
            # Update widgets
            if self.movementMode == 'Orbit:':
                pos.assign(self.posOffset)
                hpr.assign(ZERO_VEC)
                scale.assign(np.getScale())
            elif self.refCS:
                pos.assign(np.getPos(self.refCS))
                hpr.assign(np.getHpr(self.refCS))
                scale.assign(np.getScale())
        self.updatePosWidgets(pos)
        self.updateHprWidgets(hpr)
        self.updateScaleWidgets(scale)

    def updateAuxiliaryCoordinateSystems(self):
        # Temp CS
        self.tempCS.setPosHpr(self['nodePath'], 0, 0, 0, 0, 0, 0)
        # Orbit CS
        # At reference
        self.orbitFromCS.setPos(self.refCS, 0, 0, 0)
        # But aligned with target
        self.orbitFromCS.setHpr(self['nodePath'], 0, 0, 0)
        # Also update to CS
        self.orbitToCS.setPosHpr(self.orbitFromCS, 0, 0, 0, 0, 0, 0)
        # Get offset from origin
        self.posOffset.assign(self['nodePath'].getPos(self.orbitFromCS))

    ### NODE PATH TRANSFORMATION OPERATIONS ###
    def xform(self, value, axis):
        if axis in ['sx', 'sy', 'sz']:
            self.xformScale(value, axis)
        elif self.movementMode == 'Relative To:':
            self.xformRelative(value, axis)
        elif self.movementMode == 'Orbit:':
            self.xformOrbit(value, axis)
        if self.nodePathMenu.get() == 'widget':
            if ShowBaseGlobal.direct.manipulationControl.fSetCoa:
                # Update coa based on current widget position
                ShowBaseGlobal.direct.selected.last.mCoa2Dnp.assign(
                    ShowBaseGlobal.direct.widget.getMat(ShowBaseGlobal.direct.selected.last))
            else:
                # Move the objects with the widget
                ShowBaseGlobal.direct.selected.moveWrtWidgetAll()

    def xformStart(self, data):
        # Record undo point
        self.pushUndo()
        # If moving widget kill follow task and update wrts
        if self.nodePathMenu.get() == 'widget':
            taskMgr.remove('followSelectedNodePath')
            # Record relationship between selected nodes and widget
            ShowBaseGlobal.direct.selected.getWrtAll()
        # Record initial state
        self.deltaHpr = self['nodePath'].getHpr(self.refCS)
        # Update placer to reflect new state
        self.updatePlacer()

    def xformStop(self, data):
        # Throw event to signal manipulation done
        # Send nodepath as a list
        messenger.send('DIRECT_manipulateObjectCleanup', [[self['nodePath']]])
        # Update placer to reflect new state
        self.updatePlacer()
        # If moving widget restart follow task
        if self.nodePathMenu.get() == 'widget':
            # Restart followSelectedNodePath task
            ShowBaseGlobal.direct.manipulationControl.spawnFollowSelectedNodePathTask()

    def xformRelative(self, value, axis):
        nodePath = self['nodePath']
        if nodePath is not None and self.refCS is not None:
            if axis == 'x':
                nodePath.setX(self.refCS, value)
            elif axis == 'y':
                nodePath.setY(self.refCS, value)
            elif axis == 'z':
                nodePath.setZ(self.refCS, value)
            else:
                if axis == 'h':
                    self.deltaHpr.setX(value)
                elif axis == 'p':
                    self.deltaHpr.setY(value)
                elif axis == 'r':
                    self.deltaHpr.setZ(value)
                # Put node path at new hpr
                nodePath.setHpr(self.refCS, self.deltaHpr)

    def xformOrbit(self, value, axis):
        nodePath = self['nodePath']
        if nodePath is not None and self.refCS is not None and \
           self.orbitFromCS is not None and self.orbitToCS is not None:
            if axis == 'x':
                self.posOffset.setX(value)
            elif axis == 'y':
                self.posOffset.setY(value)
            elif axis == 'z':
                self.posOffset.setZ(value)
            elif axis == 'h':
                self.orbitToCS.setH(self.orbitFromCS, value)
            elif axis == 'p':
                self.orbitToCS.setP(self.orbitFromCS, value)
            elif axis == 'r':
                self.orbitToCS.setR(self.orbitFromCS, value)
            nodePath.setPosHpr(self.orbitToCS, self.posOffset, ZERO_VEC)

    def xformScale(self, value, axis):
        if self['nodePath']:
            mode = self.scalingMode.get()
            scale = self['nodePath'].getScale()
            if mode == 'Scale Free':
                if axis == 'sx':
                    scale.setX(value)
                elif axis == 'sy':
                    scale.setY(value)
                elif axis == 'sz':
                    scale.setZ(value)
            elif mode == 'Scale Uniform':
                scale.set(value, value, value)
            elif mode == 'Scale Proportional':
                if axis == 'sx':
                    sf = value / scale[0]
                elif axis == 'sy':
                    sf = value / scale[1]
                elif axis == 'sz':
                    sf = value / scale[2]
                scale = scale * sf
            self['nodePath'].setScale(scale)

    def updatePosWidgets(self, pos):
        self.posX.set(pos[0])
        self.posY.set(pos[1])
        self.posZ.set(pos[2])

    def updateHprWidgets(self, hpr):
        self.hprH.set(hpr[0])
        self.hprP.set(hpr[1])
        self.hprR.set(hpr[2])

    def updateScaleWidgets(self, scale):
        self.scaleX.set(scale[0])
        self.scaleY.set(scale[1])
        self.scaleZ.set(scale[2])

    def zeroAll(self):
        self.xformStart(None)
        self.updatePosWidgets(ZERO_VEC)
        self.updateHprWidgets(ZERO_VEC)
        self.updateScaleWidgets(UNIT_VEC)
        self.xformStop(None)

    def zeroPos(self):
        self.xformStart(None)
        self.updatePosWidgets(ZERO_VEC)
        self.xformStop(None)

    def zeroHpr(self):
        self.xformStart(None)
        self.updateHprWidgets(ZERO_VEC)
        self.xformStop(None)

    def unitScale(self):
        self.xformStart(None)
        self.updateScaleWidgets(UNIT_VEC)
        self.xformStop(None)

    def updateResetValues(self, nodePath):
        self.initPos.assign(nodePath.getPos())
        self.posX['resetValue'] = self.initPos[0]
        self.posY['resetValue'] = self.initPos[1]
        self.posZ['resetValue'] = self.initPos[2]
        self.initHpr.assign(nodePath.getHpr())
        self.hprH['resetValue'] = self.initHpr[0]
        self.hprP['resetValue'] = self.initHpr[1]
        self.hprR['resetValue'] = self.initHpr[2]
        self.initScale.assign(nodePath.getScale())
        self.scaleX['resetValue'] = self.initScale[0]
        self.scaleY['resetValue'] = self.initScale[1]
        self.scaleZ['resetValue'] = self.initScale[2]

    def resetAll(self):
        if self['nodePath']:
            self.xformStart(None)
            self['nodePath'].setPosHprScale(
                self.initPos, self.initHpr, self.initScale)
            self.xformStop(None)

    def resetPos(self):
        if self['nodePath']:
            self.xformStart(None)
            self['nodePath'].setPos(self.initPos)
            self.xformStop(None)

    def resetHpr(self):
        if self['nodePath']:
            self.xformStart(None)
            self['nodePath'].setHpr(self.initHpr)
            self.xformStop(None)

    def resetScale(self):
        if self['nodePath']:
            self.xformStart(None)
            self['nodePath'].setScale(self.initScale)
            self.xformStop(None)

    def pushUndo(self, fResetRedo = 1):
        ShowBaseGlobal.direct.pushUndo([self['nodePath']])

    def undoHook(self, nodePathList = []):
        # Reflect new changes
        self.updatePlacer()

    def pushUndoHook(self):
        # Make sure button is reactivated
        self.undoButton.configure(state = 'normal')

    def undoListEmptyHook(self):
        # Make sure button is deactivated
        self.undoButton.configure(state = 'disabled')

    def pushRedo(self):
        ShowBaseGlobal.direct.pushRedo([self['nodePath']])

    def redoHook(self, nodePathList = []):
        # Reflect new changes
        self.updatePlacer()

    def pushRedoHook(self):
        # Make sure button is reactivated
        self.redoButton.configure(state = 'normal')

    def redoListEmptyHook(self):
        # Make sure button is deactivated
        self.redoButton.configure(state = 'disabled')

    def printNodePathInfo(self):
        np = self['nodePath']
        if np:
            name = np.getName()
            pos = np.getPos()
            hpr = np.getHpr()
            scale = np.getScale()
            posString = '%.2f, %.2f, %.2f' % (pos[0], pos[1], pos[2])
            hprString = '%.2f, %.2f, %.2f' % (hpr[0], hpr[1], hpr[2])
            scaleString = '%.2f, %.2f, %.2f' % (scale[0], scale[1], scale[2])
            print('NodePath: %s' % name)
            print('Pos: %s' % posString)
            print('Hpr: %s' % hprString)
            print('Scale: %s' % scaleString)
            print(('%s.setPosHprScale(%s, %s, %s)' %
                   (name, posString, hprString, scaleString)))

    def onDestroy(self, event):
        # Remove hooks
        for event, method in self.undoEvents:
            self.ignore(event)
        self.tempCS.removeNode()
        self.orbitFromCS.removeNode()
        self.orbitToCS.removeNode()


def place(nodePath):
    return Placer(nodePath = nodePath)
